use std::fmt;
use std::time::{Duration, Instant};

pub struct StopWatch {
    id: String,
    task_list: Option<Vec<TaskInfo>>,
    start_time: Option<Instant>,
    current_task_name: Option<String>,
    last_task_info: Option<TaskInfo>,
    task_count: usize,
    total_time: Duration,
}

impl StopWatch {
    pub fn new(id: &str) -> Self {
        StopWatch {
            id: id.to_string(),
            task_list: Some(Vec::new()),
            start_time: None,
            current_task_name: None,
            last_task_info: None,
            task_count: 0,
            total_time: Duration::new(0, 0),
        }
    }

    pub fn get_id(&self) -> &str {
        &self.id
    }

    pub fn set_keep_task_list(&mut self, keep: bool) {
        self.task_list = if keep { Some(Vec::new()) } else { None };
    }

    pub fn start(&mut self) {
        self.start_with_name("");
    }

    pub fn start_with_name(&mut self, task_name: &str) {
        if self.current_task_name.is_some() {
            panic!("Can't start StopWatch: it's already running");
        } else {
            self.current_task_name = Some(task_name.to_string());
            self.start_time = Some(Instant::now());
        }
    }

    pub fn stop(&mut self) {
        if self.current_task_name.is_none() {
            panic!("Can't stop StopWatch: it's not running");
        } else {
            let last_time = self.start_time.unwrap().elapsed();
            self.total_time += last_time;
            let task_info = TaskInfo::new(self.current_task_name.clone().unwrap(), last_time);
            self.last_task_info = Some(task_info.clone());
            if let Some(ref mut task_list) = self.task_list {
                task_list.push(task_info);
            }
            self.task_count += 1;
            self.current_task_name = None;
        }
    }

    pub fn is_running(&self) -> bool {
        self.current_task_name.is_some()
    }

    pub fn current_task_name(&self) -> Option<&str> {
        self.current_task_name.as_deref()
    }

    /* pub fn last_task_info(&self) -> &TaskInfo {
        self.last_task_info.as_ref().expect("No tasks run")
    }

    pub fn get_task_info(&self) -> &[TaskInfo] {
        self.task_list
            .as_ref()
            .expect("Task info is not being kept!")
    }*/

    pub fn get_task_count(&self) -> usize {
        self.task_count
    }

    pub fn get_total_time_nanos(&self) -> u128 {
        self.total_time.as_nanos()
    }

    pub fn get_total_time_millis(&self) -> u128 {
        self.total_time.as_millis()
    }

    pub fn get_total_time_seconds(&self) -> f64 {
        self.total_time.as_secs_f64()
    }

    pub fn pretty_print(&self) -> String {
        let mut sb = String::new();
        sb.push_str(&format!("StopWatch '{}': ", self.get_id()));
        sb.push_str(&format!("{} seconds", self.get_total_time_seconds()));
        sb.push('\n');
        if let Some(ref task_list) = self.task_list {
            sb.push_str(&format!("{:<12}  %       Task name\n", "Seconds"));
            sb.push_str(&"-".repeat(40));
            sb.push('\n');
            for task in task_list {
                sb.push_str(&format!(
                    "{:<14}  {:<8}  {}\n",
                    task.get_time_seconds(),
                    format!(
                        "{:.2}%",
                        (task.get_time_seconds() / self.get_total_time_seconds()) * 100.0
                    ),
                    task.get_task_name()
                ));
            }
        } else {
            sb.push_str("No task info kept");
        }
        print!("{}", sb);
        sb
    }

    pub fn short_summary(&self) -> String {
        format!(
            "StopWatch '{}': {} seconds",
            self.get_id(),
            self.get_total_time_seconds()
        )
    }
}

#[derive(Clone)]
struct TaskInfo {
    task_name: String,
    time: Duration,
}

impl TaskInfo {
    pub fn new(task_name: String, time: Duration) -> Self {
        TaskInfo { task_name, time }
    }

    pub fn get_task_name(&self) -> &str {
        &self.task_name
    }

    pub fn get_time_nanos(&self) -> u128 {
        self.time.as_nanos()
    }

    pub fn get_time_millis(&self) -> u128 {
        self.time.as_millis()
    }

    pub fn get_time_seconds(&self) -> f64 {
        self.time.as_secs_f64()
    }
}

impl fmt::Display for StopWatch {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut sb = String::new();
        sb.push_str(&self.short_summary());
        if let Some(ref task_list) = self.task_list {
            for task in task_list {
                sb.push_str(&format!(
                    "; [{}] took {} seconds = {:.2}%",
                    task.get_task_name(),
                    task.get_time_seconds(),
                    (task.get_time_seconds() / self.get_total_time_seconds()) * 100.0
                ));
            }
        } else {
            sb.push_str("; no task info kept");
        }
        write!(f, "{}", sb)
    }
}

#[cfg(test)]
mod tests {
    
    use super::*;
    
    #[test]
    fn test_stopwatch_pretty_print() {
        let mut stopwatch = StopWatch::new("StopWatchTest");
        stopwatch.start_with_name("test1");
        stopwatch.stop();
        stopwatch.start_with_name("test2");
        stopwatch.stop();
        stopwatch.pretty_print();
        // assert_eq!(
        //     stopwatch.pretty_print(),
        //     "StopWatch 'test': 0 seconds\n-".repeat(40)
        // );
    }
    
}